feat(ebpf): use bpf_loop for local d_path implementation#175
Conversation
c2f31a8 to
3edf813
Compare
ovalenti
left a comment
There was a problem hiding this comment.
I read it carefully, and it made sense !
fact-ebpf/src/bpf/d_path.h
Outdated
There was a problem hiding this comment.
How much if BPF_CORE_READ/BPF_CORE_READ_INFO is actually needed in this patch? I've tried to use bpf_get_current_task_btf, other structs seems to be already BTF enhanced -- and at least on Fedora simple access without the macro works fine.
There was a problem hiding this comment.
I can try it out but I can already think of a couple reason why this might not work:
- A lot of the logic in this
d_pathimplementation relies on thecontainer_ofcast tostruct mount, I'm fairly certain that call loses all BTF information, but I need to test it. - Going through
bpf_loopmeans we use an intermediate struct that needs casting, fairly certain that also messes with the BTF information of types.
There was a problem hiding this comment.
I tried it and I get verifier errors when I remove the BPF_CORE_READ* macros, for now I propose we keep them as they are.
While checking this I realized I missed the stop condition for when the root of the process filesystem is reached, I will add it as part of a follow up commit.
| return 1; | ||
| } | ||
|
|
||
| if (dentry == parent) { |
There was a problem hiding this comment.
Do I understand correctly, that this case is not considered to be a "success"? If so, why?
There was a problem hiding this comment.
The successful case for d_path is to reach the mount root of the process that is currently running, this condition happens when something goes wrong traversing the mounts and we end up in a position where there are not more parents in the directory cache (usually reaching the root of the device).
I can add some clarifying comments based on the kernel implementation of d_path
fact-ebpf/src/bpf/d_path.h
Outdated
There was a problem hiding this comment.
I overlooked this originally, but I think I need some explanation about this part. I assume the intention is to get the offset that fits into PATH_MAX, right? If yes, it seems to work only because PATH_MAX value is 2^n, so that PATH_MAX - 1 will be all ones. But I don't get it how it supposed to work, if buflen is larger than PATH_MAX -- e.g. if I call d_path as:
d_path(&task->mm->exe_file->f_path, p->lineage[i].exe_path, 4097, false);
Then (buflen - 1) & (PATH_MAX - 1) is 1, and the filepath is not copied:
{"timestamp":1766410173613705048,"hostname":"xxxx","process":{"comm":"fish","args":["-fish"],"exe_path":"","container_id":null,"uid":4206644,"username":"","gid":4206644,"login_uid":4206644,"pid":70608,"in_root_mount_ns":true,"lineage":[{"uid":4206644,"exe_path":""},{"uid":0,"exe_path":""}]},"file":{"Open":{"filename":"","host_file":"/file/path/test","inode":{"inode":55138507,"dev":40}}}}
There was a problem hiding this comment.
So this is mostly to keep the verifier happy. The max length of a path in Linux is defined to be 4096 bytes, which when substracting 1 from it ends up with a binary number that is all 1s as you pointed out, so doing this bitwise and is just a convenient way to have the verifier not complain about offset being unbound.
The entire logic behind this implementation relies on PATH_MAX being 4K and the buffer length provided never being greater than PATH_MAX, these are invariants and if broken, then behavior will be undefined.
I should probably do something about better documenting these invariants, at the moment they are just sitting there.
There was a problem hiding this comment.
To recapitulate the discussion afterwards, it's an equivalent of checking that buflen is less than PATH_MAX. I can imagine the latter would be more direct and readable. Any downsides of more direct approach?
Part of ROX-32059. The local implementation of d_path now uses bpf_loop, which enables it to iterate further than the previously implemented loop that did at most 16 levels of path.
Added comments to help clarify some of the logic around d_path. Used PATH_LEN_CLAMP instead of raw `& (PATH_MAX -1)` operations, documenting the macro itself to make its purpose more obvious. Restored the d_path stop condition of reaching the root of the current task fs, which was accidentally removed when moving from the original implementation.
3edf813 to
49ca519
Compare
Description
Part of ROX-32059.
The local implementation of d_path now uses bpf_loop, which enables it to iterate further than the previously implemented loop that did at most 16 levels of path.
#156 needs this change because the path_chmod hook is throwing a verifier error with too many instructions.
Checklist
Automated testing
If any of these don't apply, please comment below.
Testing Performed